﻿//TODO: implement other dithering methods or rasterization e.g. floyd-steinberg

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace LowLevelGraphics.Filter
{
    /// <summary>
    /// Dither modes
    /// </summary>
    public enum DITHER_MODE
    {
        DEFAULT,
        FLOYD_STEINBERG,
        JAVRIS,
        STUCKI,
        FAN,
        STEPHENSON,
        BURKES,
        HALFTONE, 
        BAYER,
        RANDOM,
        VEROYVKA_BUCHANAN,
        APW,
        SIERRA,
        ATKINSON,
        ORDERED_COLOR
    }

    /// <summary>
    /// Class for dithering
    /// </summary>
    public class Dither : BaseForeBackgroundColorFilter
    {
        protected int[,] m_aDitheringArray = null;
        protected float m_fKernelSum = -1.0f;

        protected float[,] m_aDitherKernelFloydSteinberg = new float[,] {
            {0, 0, 7/16},
            {3/16, 5/16, 1/16}
        };

        protected float[,] m_aDitherKernelJarvisJudiceNinke = new float[,] {
            {0, 0, 0, 7/48, 5/48},
            {3/48, 5/48, 7/48, 5/48, 3/48},
            {1/48, 3/48, 5/48, 3/48,1/48}
        };

        protected float[,] m_aDitherKernelStucki = new float[,]{
            {0, 0, 0, 7/42, 5/24},
            {2/42, 4/42, 8/42, 4/42, 2/42},
            {1/42, 2/42, 4/42, 2/42, 1/42}
        };

        protected float[,] m_aDitherKernelSierra = new float[,]{
            {0, 0, 5/32, 3/32, 3/32},
            {2/32, 4/32, 5/32, 4/32, 2/32},
            {0, 2/32, 3/32, 2/32, 0}
        };

        protected float[,] m_aDitherKernelBurkes = new float[,]{
            {0, 0, 0, 8/32, 4/32},
            {2/32, 4/32, 8/32, 4/32, 2/32}
        };

        protected float[,] m_aCoefficients = null;
        protected DITHER_MODE M_MODE = DITHER_MODE.DEFAULT;
        protected IEnumerable<Color> m_aPalette = new List<Color>() { };

        /// <summary>
        /// default constructor
        /// </summary>
        public Dither()
        {
            KernelSum();
        }

        /// <summary>
        /// default constructor
        /// </summary>
        public Dither(DITHER_MODE _MODE)
            : this()
        {
            M_MODE = _MODE;
        }

        /// <summary>
        /// used by clone to create a deep copy
        /// </summary>
        /// <param name="_dither"></param>
        internal Dither(Dither _dither)
        {
            M_MODE = _dither.M_MODE;
        }

        /// <summary>
        /// Gets or sets dither mode
        /// </summary>
        public DITHER_MODE MODE
        {
            get { return M_MODE; }
            set { M_MODE = value; }
        }

        /// <summary>
        /// Gets or sets coefficients
        /// </summary>
        public float[,] Coefficients
        {
            get { return m_aCoefficients; }
            set { m_aCoefficients = value; }
        }

        /// <summary>
        /// Execute this Filter
        /// A Debug Statement is written if a concrete Filter is called
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <returns></returns>
        public override UnsafeBitmap Execute(UnsafeBitmap _bitmap)
        {
            switch(M_MODE)
            {
                case DITHER_MODE.DEFAULT:
                    this.m_UnsafeBitmap = Dithering(_bitmap);
                    break;

                case DITHER_MODE.FLOYD_STEINBERG:
                    this.m_aCoefficients = m_aDitherKernelFloydSteinberg;
                    Diffuse(m_aCoefficients);
                    break;

                case DITHER_MODE.STUCKI:
                    this.m_aCoefficients = m_aDitherKernelStucki;
                    Diffuse(m_aCoefficients);
                    break;

                case DITHER_MODE.FAN:
                    //this.m_UnsafeBitmap = Fan(_bitmap);
                    break;

                case DITHER_MODE.STEPHENSON:
                    //this.m_UnsafeBitmap = Stephenson(_bitmap);
                    break;

                case DITHER_MODE.BURKES:
                    this.m_aCoefficients = m_aDitherKernelBurkes;
                    Diffuse(m_aCoefficients);
                    break;

                case DITHER_MODE.HALFTONE:
                    break;

                case DITHER_MODE.BAYER:
                    break;

                case DITHER_MODE.JAVRIS:
                    break;

                case DITHER_MODE.RANDOM:
                    break;

                case DITHER_MODE.VEROYVKA_BUCHANAN:
                    break;

                case DITHER_MODE.APW:
                    break;

                case DITHER_MODE.SIERRA:
                    Diffuse(m_aDitherKernelSierra);
                    break;

                case DITHER_MODE.ATKINSON:
                    break;

                case DITHER_MODE.ORDERED_COLOR:
                    OrderedColorDithering(_bitmap);
                    break;
            }
            return _bitmap;
        }

        private void Diffuse(float[,] _aDitherKernel)
        {
        }

        private void OrderedColorDithering(UnsafeBitmap _bitmap)
        {

        }

        /// <summary>
        /// Do the dithering
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <returns></returns>
        protected UnsafeBitmap Dithering(UnsafeBitmap _bitmap)
        {
            UnsafeBitmap bitmapNew = DefaultDithering(ref _bitmap);
            return bitmapNew;
        }

        /// <summary>
        /// Floyd Steinberg Dithering
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <returns></returns>
        //private UnsafeBitmap FloydSteinbergDithering(UnsafeBitmap _bitmap)
        //{
        //    int nHeight = 0;
        //    int nWidth = 0;
        //    int x = 0;
        //    int y = 0;
        //    int nDistance = 0;
        //    for (y = 0; y < nHeight; y++)
        //    {
        //        for (x = 0; x < nWidth; x++)
        //        {
        //            nDistance = 0;
        //            Color color = _bitmap.GetPixel(x, y);
        //            //BestFittingColor(ref color, nMaxDistance, nTempDistance, nDistance, colorNew);
        //            ComputeDisperseError(_bitmap, x, y);
        //        }
        //    }
        //    return _bitmap;
        //}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        //private static byte plus_truncate_uchar(byte a, int b)
        //{
        //    if ((a & 0xff) + b < 0)
        //        return 0;
        //    else if ((a & 0xff) + b > 255)
        //        return (byte)255;
        //    else
        //        return (byte)(a + b);
        //}

        /// <summary>
        /// <<compute and disperse error macro>>=
        /// </summary>
        //private void ComputeDisperseError(UnsafeBitmap image, int x, int y)
        //{
        //    /*int i = 0;
        //    int error = (currentPixel.channels[i] & 0xff) - (m_aPalette[index].channels[i] & 0xff);
        //    if (x + 1 < image[0].length) {
        //        image[y + 0, x + 1, i] =
        //            plus_truncate_uchar(image[y + 0, x + 1, i], (error * 7) >> 4);
        //    }
        //    if (y + 1 < image.length)
        //    {
        //        if (x - 1 > 0)
        //        {
        //            image[y + 1, x - 1, i] =
        //                plus_truncate_uchar(image[y + 1, x - 1,i], (error * 3) >> 4);
        //        }
        //        image[y + 1, x + 0, i] =
        //            plus_truncate_uchar(image[y + 1, x + 0, i], (error * 5) >> 4);
        //        if (x + 1 < image[0].length)
        //        {
        //            image[y + 1, x + 1, i] =
        //                plus_truncate_uchar(image[y + 1, x + 1, i], (error * 1) >> 4);
        //        }
        //    }*/
        //}

        /// <summary>
        /// Default Dithering für M_MODE = DEFAULT
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <returns></returns>
        private UnsafeBitmap DefaultDithering(ref UnsafeBitmap _bitmap)
        {
            UnsafeBitmap bitmapNew = _bitmap;
            _bitmap = (UnsafeBitmap)_bitmap.Clone();
            int nHeight = _bitmap.Height;
            int nWidth = _bitmap.Width;
            m_aDitheringArray = new int[nHeight, nWidth];
            int nColumn = 0, nLine = 0, temp = 0;
            int nRed = 0, nGreen = 0, nBlue = 0, nGray = 0;

            Color color = Color.Transparent;
            for (nColumn = 0; nColumn < nWidth; nColumn++)
            {
                for (nLine = 0; nLine < nHeight; nLine++)
                {
                    color = _bitmap.GetPixel(nColumn, nLine);
                    nRed = color.R;
                    nGreen = color.G;
                    nBlue = color.B;
                    nGray = ColorFunctions.CalculateGrayValueMean(nRed, nGreen, nBlue);
                    m_aDitheringArray[nLine, nColumn] = nGray;
                }
            }

            for (nLine = 1; nLine < nHeight - 1; nLine++)
            {
                for (nColumn = 1; nColumn < nWidth - 1; nColumn++)
                {
                    CalculateDithering(nLine, nColumn);
                }
            }

            Color pixel = Color.Transparent;
            for (nColumn = 0; nColumn < nWidth; nColumn++)
            {
                for (nLine = 0; nLine < nHeight; nLine++)
                {
                    pixel = _bitmap.GetPixel(nColumn, nLine);
                    temp = m_aDitheringArray[nLine, nColumn];
                    temp = ((temp == 0) ? 0 : 255); //changed
                    pixel = Color.FromArgb(temp, temp, temp);
                    bitmapNew.SetPixel(nColumn, nLine, pixel);
                }
            }
            return bitmapNew;
        }


        /// <summary>
        /// Bests the color of the fitting.
        /// </summary>
        /// <param name="color">The color.</param>
        /// <param name="nMaxDistance">The n max distance.</param>
        /// <param name="nTempDistance">The n temp distance.</param>
        /// <param name="nDistance">The n distance.</param>
        /// <param name="colorNew">The color new.</param>
        //private void BestFittingColor(ref Color color, int nMaxDistance, ref int nTempDistance, ref int nDistance, ref Color colorNew)
        //{
        //    nDistance = nMaxDistance;

        //    if (m_aPalette.Contains(color))
        //    {
        //        return;
        //    }

        //    foreach (Color colorTemp in m_aPalette)
        //    {
        //        nTempDistance = ColorFunctions.DistanceSquared(color, colorTemp);
        //        if (nTempDistance < nDistance)
        //        {
        //            nDistance = nTempDistance;
        //            colorNew = colorTemp;
        //            if (nDistance == 0)
        //            {
        //                break;
        //            }
        //        }
        //    }
        //}

        /// <summary>
        /// Calculate the dithering
        /// </summary>
        /// <param name="_nY"></param>
        /// <param name="_nX"></param>
        protected void CalculateDithering(int _nY, int _nX)
        {
            int Teiler = 0;
            if (m_aDitheringArray[_nY, _nX] < 128)
            {
                Teiler = m_aDitheringArray[_nY, _nX] / 16;
                m_aDitheringArray[_nY, _nX] = 0;
            }
            else
            {
                Teiler = (m_aDitheringArray[_nY, _nX] - 255) / 16;
                m_aDitheringArray[_nY, _nX] = 1;
            }
            m_aDitheringArray[_nY + 1, _nX - 1] += (Teiler * 3);
            m_aDitheringArray[_nY + 1, _nX] += (Teiler * 5);
            m_aDitheringArray[_nY + 1, _nX + 1] += Teiler;
            m_aDitheringArray[_nY, _nX + 1] += (Teiler * 7);
        }

        //public TiledImage applyFilter(TiledImage src, TiledImage dest)
        //{
        //    if (dest == null)
        //        dest = new TiledImage(src, false);

        //    int maxX = src.getWidth();
        //    int maxY = src.getHeight();
        //    errors = new float[src.getHeight() * src.getWidth()];

        //    if (shades < 2)
        //        shades = 2;
        //    float factor = (float)(shades - 1) / (float)255;

        //    for (int ch = 0; ch < src.getNumBands(); ch++)
        //    {
        //        for (int x = 0; x < maxX; x++)
        //            for (int y = 0; y < maxY; y++)
        //            {
        //                float newval = src.getSample(x, y, ch) + errors[y * src.getHeight() + x];

        //                newval *= factor;
        //                newval = (float)(Math.floor(newval + 0.5));
        //                newval /= factor;

        //                if (newval > 255)
        //                    newval = 255;
        //                if (newval < 0)
        //                    newval = 0;

        //                dest.setSample(x, y, ch, (int)(newval + 0.5));

        //                float cerror = src.getSample(x, y, ch) + errors[y * src.getHeight() + x]
        //                               - dest.getSample(x, y, ch);

        //                if (x > 1 && x < maxX && y < maxY - 2)
        //                    errors[(y + 2) * src.getHeight() + x - 2] += cerror * 1.0f / 42.0f;

        //                if (x > 0 && x < maxX && y < maxY - 2)
        //                    errors[(y + 2) * src.getHeight() + x - 1] += cerror * 2.0f / 42.0f;

        //                if (x > 1 && x < maxX - 2 && y < maxY - 1)
        //                    errors[(y + 1) * src.getHeight() + x - 2] += cerror * 2.0f / 42.0f;

        //                if (x > 0 && x < maxX - 2 && y < maxY - 2)
        //                    errors[(y + 1) * src.getHeight() + x - 1] += cerror * 4.0f / 42.0f;

        //                if (y < maxY && x < maxX - 1)
        //                    errors[y * src.getHeight() + x + 1] += cerror * 8.0f / 42.0f;

        //                if (y < maxY && x < maxX - 2)
        //                    errors[y * src.getHeight() + x + 2] += cerror * 4.0f / 42.0f;

        //                if (y < maxY - 1 && x < maxX)
        //                    errors[(y + 1) * src.getHeight() + x] += cerror * 8.0f / 42.0f;

        //                if (y < maxY - 2 && x < maxX)
        //                    errors[(y + 2) * src.getHeight() + x] += cerror * 4.0f / 42.0f;

        //                if (x < maxX - 1 && y < maxY - 1)
        //                    errors[(y + 1) * src.getHeight() + x + 1] += cerror * 4.0f / 42.0f;

        //                if (x < maxX - 2 && y < maxY - 1)
        //                    errors[(y + 1) * src.getHeight() + x + 2] += cerror * 2.0f / 42.0f;

        //                if (x < maxX - 1 && y < maxY - 2)
        //                    errors[(y + 2) * src.getHeight() + x + 1] += cerror * 2.0f / 42.0f;

        //                if (x < maxX - 2 && y < maxY - 2)
        //                    errors[(y + 2) * src.getHeight() + x + 2] += cerror * 1.0f / 42.0f;

        //            }
        //    }

        //    return dest;
        //}

        ///**
        // * This method returns number of shades generated by Stucki
        // * algorithm in image dithering process. Default returned value
        // * is 2.
        // * 
        // * @return Number of shades.
        // */
        //public int getShades()
        //{
        //    return shades;
        //}

        ///**
        // * This method sets number of shades generated by Stucki
        // * algorithm in image dithering process.
        // * 
        // * @param shades New number of shades.
        // */
        //public void setShades(int shades)
        //{
        //    this.shades = shades;
        //}

        /// <summary>
        /// Calculate coefficents kernel sum
        /// </summary>
        private void KernelSum()
        {
            m_fKernelSum = 0;
            foreach (float fValue in m_aCoefficients)
            {
                m_fKernelSum += fValue;
            }
        }

        /// <summary>
        /// Clones this filter
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new Dither(this);
        }
    }
}
